<?php

/**
 * Base controller functionality
 * @package Nina
 * @author John Main<john@thedaddy.org>
 */
abstract class Nina_Controller
{
	protected $title = APP_NAME;
	protected $controller;
	protected $action;
	protected $id;
	protected $layout = false;
	protected $user;
	protected $client;
	protected $request;
	protected $version;
	protected $reg;
	protected $message;
	protected $logger;
	protected $renderlayout = true;
	protected $renderview = true;
	protected $inline_styling = false;
	protected $view;
	protected $anchor;
	protected $vars = array();
	protected $meta = array();
	protected $meta_property = array();
	protected $style = array('main.css' => 'all');
	protected $js = array();
	protected $user_actions = array('doLogout', 'doChangePassword', 'doClientLogout', 'doClientChangePassword');
	protected $admin_actions = array('editSettings');
	protected $super_actions = array();
	protected $uber_actions = array();
	protected $breadcrumbs = array();
	protected $mandatory_js_ignore = array();
	protected $mandatory_style_ignore = array();
	protected $login_redirect = '/';
	protected $show_messages = true;

	/**
	 * Constructor
	 * @param string $controller The controller name
	 * @param string $action The action name
	 * @param integer $id The ID
	 */
	public function __construct($controller, $action, $id) {
		$this->reg = Nina_Registry::getInstance();
		$this->user = $this->reg->get("runtime.user");
		$this->client = $this->reg->get("runtime.client");
		$this->request = $this->reg->get("runtime.request");
		$this->version = $this->reg->get('core.version');
		$this->reg->set("runtime.controller", $this);
		$this->message = new Nina_Message();
		$this->logger = Nina_Logger::getInstance();

		$this->setController($controller);
		$this->setAction($action);
		$this->setId($id);

		if(file_exists(WEB_ROOT.'/css/'.$controller.'.css'))
			$this->addStyle($controller.'.css');

		if(file_exists(WEB_ROOT.'/js/'.$controller.'.js'))
			$this->addJs($controller.'.js');
	}

	/**
	 * Get the current action
	 * @return string
	 */
	public function getAction() {
		return $this->action;
	}

	/**
	 * Set the action
	 * @param string [$action='index'] The action
	 */
	protected function setAction($action = 'index') {

		if(!$this->hasAction($action))
			$action = 'index';
		elseif(!$this->checkUserAction($action)) {
			$this->setLoginRedirect();
			$action = 'login';
		}
		elseif(!$this->checkAdminAction($action)) {
			$this->addMessage('You must have admin privileges to perform that action');
			$action = 'index';
		}
		elseif(!$this->checkSuperAction($action)) {
			$this->addMessage('You must have super user privileges to perform that action');
			$action = 'index';
		}
		elseif(!$this->checkUberAction($action)) {
			$this->addMessage('You must have uber user privileges to perform that action');
			$action = 'index';
		}

		$this->action = $action;
	}

	/**
	 * Get the current controller name
	 * @return string
	 */
	public function getName() {
		return $this->controller;
	}

	/**
	 * Get the current controller name
	 * @return string
	 */
	public function getController() {
		return $this->controller;
	}

	/**
	 * Set the controller
	 * @param string $controller The controller
	 */
	protected function setController($controller) {
		$this->controller = $controller;
	}

	/**
	 * Get the current ID
	 * @return integer
	 */
	public function getId() {
		return $this->id;
	}

	/**
	 * Set the ID
	 * @param integer $id The ID
	 */
	protected function setId($id) {
		$this->id = $id;
	}

	/**
	 * Get the layout
	 * @return string|false
	 */
	protected function getLayout($layout) {
		return $this->layout;
	}

	/**
	 * Override the layout
	 * @param string $layout Layout
	 */
	protected function setLayout($layout) {
		$this->layout = $layout;
	}

	/**
	 * Set whether or not to render the layout
	 * @param boolean [$render=true] Render the layout
	 */
	protected function setRenderLayout($render = true) {
		$this->renderlayout = $render;
	}

	/**
	 * Set whether or not to render the view
	 * @param boolean [$render=true] Render the view
	 */
	protected function setRenderView($render = true) {
		$this->renderview = $render;
	}

	/**
	 * Find the current view template file
	 * @param string [$view=null] View file if not using controller default
	 * @return string
	 */
	public function getView($view = null) {
		if($view) {
			if(file_exists($view))
				return $view;
			elseif (file_exists(APP_ROOT.'/views/'.$this->getName().'/'.$view.'.php'))
				return APP_ROOT.'/views/'.$this->getName().'/'.$view.'.php';
			elseif (file_exists(PARENT_ROOT.'/views/'.$this->getName().'/'.$view.'.php'))
				return PARENT_ROOT.'/views/'.$this->getName().'/'.$view.'.php';
			elseif (file_exists(APP_ROOT.'/views/'.$this->reg->get('core.default.controller').'/'.$view.'.php'))
				return APP_ROOT.'/views/'.$this->reg->get('core.default.controller').'/'.$view.'.php';
			elseif (file_exists(PARENT_ROOT.'/views/'.$this->reg->get('core.default.controller').'/'.$view.'.php'))
				return PARENT_ROOT.'/views/'.$this->reg->get('core.default.controller').'/'.$view.'.php';
		}
		else {
			if(file_exists($this->view))
				return $this->view;
			elseif (file_exists(APP_ROOT.'/views/'.$this->getName().'/'.$this->view.'.php'))
				return APP_ROOT.'/views/'.$this->getName().'/'.$this->view.'.php';
			elseif (file_exists(PARENT_ROOT.'/views/'.$this->getName().'/'.$this->view.'.php'))
				return PARENT_ROOT.'/views/'.$this->getName().'/'.$this->view.'.php';
			elseif (file_exists(APP_ROOT.'/views/'.$this->getName().'/'.$this->getAction().'.php'))
				return APP_ROOT.'/views/'.$this->getName().'/'.$this->getAction().'.php';
			elseif (file_exists(PARENT_ROOT.'/views/'.$this->getName().'/'.$this->getAction().'.php'))
				return PARENT_ROOT.'/views/'.$this->getName().'/'.$this->getAction().'.php';
			elseif (file_exists(APP_ROOT.'/views/'.$this->reg->get('core.default.controller').'/'.$this->getAction().'.php'))
				return APP_ROOT.'/views/'.$this->reg->get('core.default.controller').'/'.$this->getAction().'.php';
			elseif (file_exists(PARENT_ROOT.'/views/'.$this->reg->get('core.default.controller').'/'.$this->getAction().'.php'))
				return PARENT_ROOT.'/views/'.$this->reg->get('core.default.controller').'/'.$this->getAction().'.php';
		}

		throw new Nina_Exception_Controller('Unable to find view template');
	}

	/**
	 * Find out if we have a valid view
	 * @param string [$view=null] View file if not using controller default
	 * @return boolean
	 */
	public function hasView($view = null) {
		if($view) {
			if(file_exists($view))
				return true;
			elseif (file_exists(APP_ROOT.'/views/'.$this->getName().'/'.$view.'.php'))
				return true;
			elseif (file_exists(PARENT_ROOT.'/views/'.$this->getName().'/'.$view.'.php'))
				return true;
		}
		else {
			if(file_exists($this->view))
				return true;
			elseif (file_exists(APP_ROOT.'/views/'.$this->getName().'/'.$this->view.'.php'))
				return true;
			elseif (file_exists(APP_ROOT.'/views/'.$this->getName().'/'.$this->getAction().'.php'))
				return true;
			elseif (file_exists(PARENT_ROOT.'/views/'.$this->getName().'/'.$this->view.'.php'))
				return true;
			elseif (file_exists(PARENT_ROOT.'/views/'.$this->getName().'/'.$this->getAction().'.php'))
				return true;
		}

		return false;
	}

	/**
	 * Set the view
	 * @param string $view The view name or file path
	 */
	protected function setView($view) {
		$this->view = $view;
	}

	/**
	 * Set the anchor ready for a redirect
	 * @param string $anchor The anchor
	 */
	protected function setAnchor($anchor) {
		$this->anchor = $anchor;
	}

	/**
	 * Check if an action exists on this controller
	 * @param string $action The action
	 */
	protected function hasAction($action) {
		return method_exists($this, $action);
	}

	/**
	 * Call an action
	 * @param string [$action='index'] The action
	 * @param boolean [$hard=false] Do a hard redirect
	 * @param boolean [$clear_id=false] Clear the ID value
	 */
	public function callAction($action = 'index', $hard = false, $clear_id = false) {

		if($clear_id)
			$this->id = 0;

		if($hard) {
			session_write_close();
			header('Location: '.Nina_Links::url($this->getController(), $action, $this->getId(), array(), $this->anchor));
			exit();
		}
		else {
			$this->setAction($action);
			$function = $this->getAction();
			$this->$function();
		}
	}

	/**
	 * Call an action with a force redirect
	 * @param string [$action='index'] The action
	 * @param boolean [$clear_id=false] Clear the ID value
	 */
	public function callActionHard($action = 'index', $clear_id = false) {
		$this->callAction($action, true, $clear_id);
	}

	/**
	 * Forward to a new controller
	 * @param string [$controller-'default'] The controller
	 * @param string [$action='index'] The action
	 * @param boolean [$id=0] The ID
	 * @param boolean [$hard=false] Do a hard redirect
	 * @param boolean [$set_in_runtime=false] Set new controller in the runtime environment so that the new controller/action view gets rendered
	 */
	public function forward($controller = 'default', $action = 'index', $id = 0, $hard = false, $set_in_runtime = false) {

		if($hard) {
			session_write_close();
			header('Location: ' . Nina_Links::url($controller, $action, $id));
			exit();
		}
		else {
			if(file_exists(APP_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php'))
				require_once APP_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php';
			elseif(file_exists(PARENT_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php'))
				require_once PARENT_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php';
			else {
				$controller = 'default';

				if(file_exists(APP_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php'))
					require_once APP_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php';
				elseif(file_exists(PARENT_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php'))
					require_once PARENT_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php';
				else
					throw new Nina_Exception_Controller("Could not find controller");
			}

			$controller_class = ucfirst($controller).'_Controller';
			$controller_instance = new $controller_class($action, $id);
			$controller_instance->callAction($action);

			if($set_in_runtime)
				$this->reg->set("runtime.controller", $controller_instance);
		}
	}

	/**
	 * Forward to a new controller with a force redirect
	 * @param string [$controller-'default'] The controller
	 * @param string [$action='index'] The action
	 * @param boolean [$id=0] The ID
	 */
	public function forwardHard($controller = 'default', $action = 'index', $id = 0) {
		$this->forward($controller, $action, $id, true);
	}

	/**
	 * Render the view
	 * @param string [$view=null] View file if not using controller default
	 */
	public function render($view = null) {
		if($this->renderview) {
			if($this->isHaml($view))
				echo Nina_Haml::renderFromController($this);
			else
				require $this->getView($view);
		}
	}

	/**
	 * Render the view to a string
	 * @param string [$view=null] View file if not using controller default
	 * @return string
	 */
	public function renderToString($view = null) {
		ob_start();
   		$this->render($view);
		return ob_get_clean();
	}

	/**
	 * Render a specific action
	 * @param string $action The action
	 */
	public function renderAction($action) {
		$this->setAction($action);
		$function = $this->getAction();
		$this->$function();
		$this->render();
	}

	/**
	 * Render a specific action to a string
	 * @param string $action The action
	 * @return string
	 */
	public function renderActionToString($action) {
		ob_start();
   		$this->renderAction($action);
		return ob_get_clean();
	}

	/**
	 * Add to the list of actions that require a logged in user
	 * @param string $action The action
	 */
	public function addUserAction($action) {
		$this->user_actions[] = $action;
	}

	/**
	 * Add to the list of actions that require a logged in user
	 * @param array $actions The actions
	 */
	public function addUserActions(array $actions) {
		$this->user_actions = array_merge($this->user_actions, $actions);
	}

	/**
	 * Check if the current user can access a user action
	 * @param string $action The action
	 */
	public function checkUserAction($action) {
		return ($this->user && (!$this->user->isGuest() || !in_array($action, $this->user_actions)));
	}

	/**
	 * Add to the list of actions that require an admin user
	 * @param string $action The action
	 */
	public function addAdminAction($action) {
		$this->admin_actions[] = $action;
	}

	/**
	 * Add to the list of actions that require an admin user
	 * @param array $actions The actions
	 */
	public function addAdminActions(array $actions) {
		$this->admin_actions = array_merge($this->admin_actions, $actions);
	}

	/**
	 * Check if the current user can access an admin action
	 * @param string $action The action
	 */
	public function checkAdminAction($action) {
		return ($this->user && ($this->user->isAdminLevel() || !in_array($action, $this->admin_actions)));
	}

	/**
	 * Add to the list of actions that require a super user
	 * @param string $action The action
	 */
	public function addSuperAction($action) {
		$this->super_actions[] = $action;
	}

	/**
	 * Add to the list of actions that require a super user
	 * @param array $actions The actions
	 */
	public function addSuperActions(array $actions) {
		$this->super_actions = array_merge($this->super_actions, $actions);
	}

	/**
	 * Check if the current user can access a super action
	 * @param string $action The action
	 */
	public function checkSuperAction($action) {
		return ($this->user && ($this->user->isSuperLevel() || !in_array($action, $this->super_actions)));
	}

	/**
	 * Add to the list of actions that require an uber user
	 * @param string $action The action
	 */
	public function addUberAction($action) {
		$this->uber_actions[] = $action;
	}

	/**
	 * Add to the list of actions that require an uber user
	 * @param array $actions The actions
	 */
	public function addUberActions(array $actions) {
		$this->uber_actions = array_merge($this->uber_actions, $actions);
	}

	/**
	 * Check if the current user can access an uber action
	 * @param string $action The action
	 */
	public function checkUberAction($action) {
		return ($this->user && ($this->user->isUber() || !in_array($action, $this->uber_actions)));
	}

	/**
	 * Check if the current view is HAML file
	 * @param string [$view=null] View file if not using controller default
	 * @return boolean
	 */
	protected function isHaml($view = null) {
		return (substr($this->getView($view), -4) == 'haml');
	}

	/**
	 * Get the title tag
	 * @return string
	 */
	public function getTitleTag() {
		return '<title>'.$this->title.' - '.APP_NAME.'</title>';
	}

	/**
	 * Add meta tag data
	 * @param string $name The name
	 * @param string $content The content
	 */
	public function addMeta($name, $content) {
		$this->meta[$name] = $content;
	}

	/**
	 * Remove meta tag data
	 * @param string $name The name
	 */
	public function removeMeta($name) {
		unset($this->meta[$name]);
	}

	/**
	 * Add meta property data
	 * @param string $name The name
	 * @param string $content The content
	 */
	public function addMetaProperty($name, $content) {
		$this->meta_property[$name] = $content;
	}

	/**
	 * Remove meta property data
	 * @param string $name The name
	 */
	public function removeMetaProperty($name) {
		unset($this->meta_property[$name]);
	}

	/**
	 * Get the meta tags
	 * @return string
	 */
	public function getMetaTag() {
		$tags = '';

		foreach ($this->meta as $name => $content) {
			$tags .= '<meta name="'.$name.'" content="'.$content.'" />' . PHP_EOL;
		}

		foreach ($this->meta_property as $property => $content) {
			$tags .= '<meta property="'.$property.'" content="'.$content.'" />' . PHP_EOL;
		}

		return $tags;
	}

	/**
	 * Set the app to render the styling inline
	 * @param boolean [$inline=true] Render inline
	 */
	protected function setInlineStyling($inline = true) {
		$this->inline_styling = $inline;
	}

	/**
	 * Check if we are rendering the styling inline
	 * @return boolean
	 */
	protected function getInlineStyling() {
		return $this->inline_styling;
	}

	/**
	 * Add a CSS file to the includes
	 * @param string $style The CSS file name
	 */
	public function addStyle($style, $media = 'all') {
		$this->style[$style] = $media;
	}

	/**
	 * Add a CSS file to the includes
	 * @param string $style The CSS file name
	 */
	public function addCss($style) {
		$this->addStyle($style);
	}

	/**
	 * Remove a CSS file from the includes
	 * @param string $style The CSS file name
	 */
	public function removeStyle($style) {
		unset($this->style[$style]);
	}

	/**
	 * Remove a CSS file from the includes
	 * @param string $style The CSS file name
	 */
	public function removeCss($style) {
		$this->removeStyle($style);
	}

	/**
	 * Inhibit a mandatory stylesheet from being included
	 * @param string $style The CSS file name
	 */
	public function removeMandatoryStyle($style) {
		$this->mandatory_style_ignore[] = $style;
	}

	/**
	 * Get the style tags
	 * @return string
	 */
	public function getStyleTag() {
		$tags = '';
		$style_folder = WEB_ROOT.DIRECTORY_SEPARATOR.CSS;
		$mandatory_style_folder = $style_folder.DIRECTORY_SEPARATOR.'mandatory';

		$version_data = ($this->version) ? '?v=' . $this->version : '';

		if($this->getInlineStyling()) {
			$tags = '';

			if(($files = @scandir($mandatory_style_folder))) {

				$tags .= '<style type="text/css" media="all">';

				foreach($files as $file) {
					if($file{0} != "." && !in_array($file, $this->mandatory_style_ignore)) {

						$tags .= file_get_contents($mandatory_style_folder . DIRECTORY_SEPARATOR . $file) . PHP_EOL;
					}
				}

				$tags .= '</style>';
			}

			foreach ($this->style as $style => $media) {
				if(file_exists($style_folder.DIRECTORY_SEPARATOR.$style)) {
					$tags .= '<style type="text/css" media="'.$media.'">';
					$tags .= file_get_contents($style_folder . DIRECTORY_SEPARATOR . $style) . PHP_EOL;
					$tags .= '</style>';
				}
			}
		}
		else {
			$css_root = Nina_Links::getUrlRoot() . str_replace('//', '/', '/'.CSS);

			if(($files = @scandir($mandatory_style_folder))) {

				foreach($files as $file) {
					if($file{0} != "." && !in_array($file, $this->mandatory_style_ignore)) {
						$tags .= '<link rel="stylesheet" type="text/css" href="'.$css_root.'/mandatory/'.$file.$version_data.'" media="all" />' . PHP_EOL;
					}
				}
			}

			foreach ($this->style as $style => $media) {
				if(file_exists($style_folder.DIRECTORY_SEPARATOR.$style)) {
					$tags .= '<link rel="stylesheet" type="text/css" href="'.$css_root.'/'.$style.$version_data.'" media="'.$media.'" />' . PHP_EOL;
				}
			}
		}

		return $tags;
	}

	/**
	 * Add a JS file to the includes
	 * @param string $js The JS file name
	 */
	public function addJs($js) {
		array_push($this->js, $js);
	}

	/**
	 * Remove a JS file from the includes
	 * @param string $js The JS file name
	 */
	public function removeJs($js) {
		$index = array_search($js, $this->js);
		if($index !== false)
			unset($this->js[$index]);
	}

	/**
	 * Inhibit a mandatory JS script from being included
	 * @param string $js The JS file name
	 */
	public function removeMandatoryJs($js) {
		$this->mandatory_js_ignore[] = $js;
	}

	/**
	 * Get the javascript tags
	 * @return string
	 */
	public function getJsTag() {
		$tags = '';

		$js_root = Nina_Links::getUrlRoot() . str_replace('//', '/', '/'.JS);

		$version_data = ($this->version) ? '?v=' . $this->version : '';

		if(($files = @scandir(WEB_ROOT.DIRECTORY_SEPARATOR.JS.DIRECTORY_SEPARATOR.'mandatory'))) {
			//$dirs = array();

			foreach($files as $file) {
				if($file{0} != "." && !in_array($file, $this->mandatory_js_ignore)) {
					$tags .= '<script language="javascript" type="text/javascript" src="'.$js_root.'/mandatory/'.$file.$version_data.'"></script>' . PHP_EOL;
				}
			}
		}

		foreach ($this->js as $js) {
			if(file_exists(WEB_ROOT.DIRECTORY_SEPARATOR.JS.DIRECTORY_SEPARATOR.$js)) {
				$tags .= '<script language="javascript" type="text/javascript" src="'.$js_root.'/'.$js.$version_data.'"></script>' . PHP_EOL;
			}
		}

		return $tags;
	}

	/**
	 * Check if we have any breadcrumbs
	 * @return boolean
	 */
	public function hasBreadcrumbs() {
		return (count($this->breadcrumbs) > 0);
	}

	/**
	 * Get the breadcrumbs
	 * @return array
	 */
	public function getBreadcrumbs() {
		return $this->breadcrumbs;
	}

	/**
	 * Set the breadcrumbs
	 * @param array $breadcrumbs The breadcrumbs
	 */
	public function setBreadcrumbs($breadcrumbs) {
		$this->breadcrumbs = $breadcrumbs;
	}

	/**
	 * Add a breadcrumb
	 * @param string $name The name
	 * @param string [$url='#'] The URL
	 */
	public function addBreadcrumb($name, $url = '#') {
		$this->breadcrumbs[$name] = $url;
	}

	/**
	 * Clear the breadcrumbs
	 */
	public function clearBreadcrumbs() {
		$this->breadcrumbs = array();
	}

	/**
	 * Set the login redirect to the current page
	 */
	public function setLoginRedirect() {
		$this->login_redirect = urlencode($_SERVER["REQUEST_URI"]);
	}

	/**
	 * Get the login redirect URL
	 * @return string
	 */
	public function getLoginRedirect() {
		return $this->login_redirect;
	}

	/**
	 * Login
	 */
	public function doLogin() {
		$username = $this->request->getParameter("username");
		$password = $this->request->getParameter("password");

		if(!$this->user->login($username, $password)) {
			if(!$this->hasMessages())
				$this->addMessage("Invalid username/password");
		}
		elseif($this->request->getParameter('path')) {
			session_write_close();
			header('Location: ' . Nina_Links::getUrlRoot() . urldecode($this->request->getParameter('path')));
			exit();
		}

		$this->forwardHard('default', 'index');
	}

	/**
	 * Logout
	 */
	public function doLogout() {
		$this->user->logout();
		$this->forwardHard();
		exit();
	}

	/**
	 * Change password
	 */
	public function doChangePassword() {
		if(($password = $this->request->getParameter("password"))) {

			if(!$this->reg->get('security.client-side-encryption'))
				$password = Nina_User::encode($password);

			$this->user->setPassword($password);
			$this->addMessage("Saved");
		}
		else
			$this->addMessage("Invalid password");

		$this->callActionHard();
	}

	/**
	 * Login as a client
	 */
	public function doClientLogin() {
		$username = $this->request->getParameter("username");
		$password = $this->request->getParameter("password");

		if(!$this->client->login($username, $password)) {
			if(!$this->hasMessages())
				$this->addMessage("Invalid username/password");
		}

		if($this->request->getParameter('path')) {
			session_write_close();
			header('Location: '.urldecode($this->request->getParameter('path')));
			exit();
		}
		else
			$this->forwardHard('default', 'index');
	}

	/**
	 * Logout as a client
	 */
	public function doClientLogout() {
		$this->client->logout();
		$this->forwardHard();
	}

	/**
	 * Change client password
	 */
	public function doClientChangePassword() {
		if(($password = $this->request->getParameter("password"))) {

			if(!$this->reg->get('security.client-side-encryption'))
				$password = Nina_User::encode($password);

			$this->client->setPassword($password);
			$this->addMessage("Saved");
		}
		else
			$this->addMessage("Invalid password");

		$this->callActionHard();
	}

	/**
	 * Activate a client
	 */
	public function doActivateClient() {
		if($client_id = $this->request->getParameter("client_id")) {
			if($client = Client::retrieveByPk(new Client(), $client_id)) {
				$client->setActivated(true);
				$client->save();

				$this->addMessage("Client account for ".$client->getName()." has been activated");
			}
			else $this->addMessage("Invalid client identification");
		}
		else $this->addMessage("Invalid client identification");

		$this->callAction();
	}

	/**
	 * Edit the settings and handle redirection
	 * This can be called as an action or as a normal function
	 * @param string [$redirect=false] Redirect
	 * @param string [$redirect_action='index'] Redirect action
	 * @param boolean [$redirect_hard=false] Hard redirect
	 * @param string [$message='Settings updated'] Update Message
	 */
	public function editSettings($redirect = true, $redirect_action = 'index', $redirect_hard = false,  $message = 'Settings updated') {
		Setting::setFromRequest();

		if($redirect) {
			if($message)
				$this->addMessage($message);

			if($redirect_hard)
				$this->callActionHard($redirect_action);
			else
				$this->callAction($redirect_action);
		}
	}

	/**
	 * 404 Error
	 * Called if you want to halt and display a 'pretty' 404 page from a controller.
	 */
	public function doError404($message = null) {
		echo "<h1 style=\"background-color: #7799aa; color: #fff;\">Page Not Found (404)</h1>";
		if ($message) {
			echo "<h2>Additional Information</h2>" . $message;
		}
		exit(1);
	}

	/**
	 * 500 Error
	 * Called when something goes badly wrong and a 500 error page is needed.
	 */
	public function doError500(Exception $e) {
		echo "<h1 style=\"background-color: #ee4444; color: #fff;\">Runtime Error (500)</h1>";
		echo "<h2>Message</h2>" . $e->getMessage();
		echo "<h3>Stack Trace</h3>" . nl2br(htmlentities($e->getTraceAsString()));
		exit(1);
	}

	/**
	 * Lower case the first letter of a string
	 * lcfirst only exists in PHP >= 5.3 so this is a backup
	 */
	public function lowerfirst($str) {
		if (function_exists('lcfirst'))
			return lcfirst( $str );
		else
			return (strtolower(substr($str,0,1)).substr($str,1));
	}

	/**
	 * Check if we are showing messages
	 * @return boolean
	 */
	public function getShowMessages() {
		return $this->show_messages;
	}

	/**
	 * Enable/disable showing of messages
	 * @param boolean [$show_messages=true] Show messages
	 */
	public function setShowMessages($show_messages = true) {
		$this->show_messages = $show_messages;
	}

	/**
	 * Add a message
	 * @param string|array $message The message text(s)
	 * @param string [$anchor='default'] Message anchor
	 * @return array
	 */
	public function addMessage($message, $anchor = 'default') {
		if($this->getShowMessages())
			$this->message->add($message, $anchor);
	}

	/**
	 * Get all messages
	 * @param string [$anchor='default'] Message anchor
	 * @return array
	 */
	public function getMessages($anchor = 'default') {
		return $this->message->get($anchor);
	}

	/**
	 * Get all messages
	 * @param string [$anchor='default'] Message anchor
	 * @return string
	 */
	public function getFirstMessage($anchor = 'default') {
		if($message = $this->message->getFirst($anchor))
			return $message;
		else
			return '';
	}

	/**
	 * Check if any messages are present.
	 * @param string [$anchor='default'] Message anchor
	 * @return boolean
	 */
	public function hasMessages($anchor = 'default') {
		return $this->message->hasMessages($anchor);
	}

	/**
	 * Clear the messages
	 */
	public function clearMessages() {
		$this->message->clear();
		$this->request->clearMessages();
	}

	/**
	 * Standard login action
	 */
	public function login() {
		$username = $this->request->getParameter("username");
		$password = $this->request->getParameter("password");

		if($this->user->login($username, $password)) {
			$this->callAction('show');
			return;
		}
		else
			$this->callAction();
	}

	/**
	 * Standard logout action
	 */
	public function logout() {
		$this->user->logout();
		$this->callAction();
	}

	/**
	 * Send unrecognised get calls to the controller vars
	 * @param string $field The field that we're looking for
	 * @return string|null
	 */
	public function __get($field) {
		if(isset($this->$field))
			return $this->$field;
		elseif(isset($this->vars[$field]))
			return $this->vars[$field];
		else
			return null;
	}

	/**
	 * Send unrecognised set calls to the controller vars
	 * @param string $field The field that we're setting
	 * @param string $value The value
	 */
	public function __set($field, $value) {
		if(isset($this->$field))
			$this->$field = $value;
		else
			$this->vars[$field] = $value;
	}

	/**
	 * Catches get and set calls **Shouldn't be necessary - above methods should work as method missings on their own
	 * @param string $function The function name
	 * @param array $args The arguments
	 * @return mixed
	 */
	public function __call($function, $args) {
		$opt = substr($function, 0, 3);
		$var = $this->lowerfirst(substr($function, 3));

		if(in_array($opt, array("get", "set"))) {
			if($opt == 'get')
				return $this->__get($var);
			elseif(isset($args[0]))
				$this->__set($var, $args[0]);
		}
		else {
			$error_message = 'Tried to call unknown method '. $function;

			if($this->reg->get('logging.soft_fail')) {
				$this->logger->error($error_message);
				return false;
			}
			else
				throw new Nina_Exception_Controller($error_message);
		}
	}
}
